package org.dromara.web.utils;

import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.pay.config.WxPayProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.io.Serial;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 微信相关工具
 *
 * @author zlj
 * @since 2023/3/8
 */
@Component
@Slf4j
public class WxXcxUtil {

    @Autowired
    private StringRedisTemplate redisTemplate;
    @Resource
    private WxPayProperties wxPayProperties;

    private static StringRedisTemplate staticRedisTemplate;

    /**
     * access_token获取计数器
     */
    private static Integer count = 0;
    /**
     * access_token获取失败尝试次数
     */
    private static final Integer TRY_COUNT = 10;

    @PostConstruct
    public void init() {
        staticRedisTemplate = redisTemplate;
        XCX_APP_ID = wxPayProperties.getMinAppId();
        XCX_APP_SECRET = wxPayProperties.getAppSecret();
    }

    private static String XCX_APP_ID;
    private static String XCX_APP_SECRET;
    private static final String XCX_LOGIN_URL = "https://api.weixin.qq.com/sns/jscode2session";
    private static final String XCX_GET_PHONE_NUMBER_URL = "https://api.weixin.qq.com/wxa/business/getuserphonenumber";
    private static final String XCX_GET_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token";
    private static final String XCX_SEND_CUSTOM_MESSAGE_URL = "https://api.weixin.qq.com/cgi-bin/message/custom/send";
    private static final String REDIS_KEY_XCX_ACCESS_TOKEN = "xcx_access_token";

    /**
     * 小程序登陆
     *
     * @param code 登陆code
     * @return 小程序响应信息
     */
    public static XcxLoginResp xcxLogin(String code) {
        Map<String, Object> param = new HashMap<>();
        param.put("appid", XCX_APP_ID);
        param.put("secret", XCX_APP_SECRET);
        param.put("grant_type", "authorization_code");
        param.put("js_code", code);
        String response = HttpUtil.get(XCX_LOGIN_URL, param);
        return JSON.parseObject(response, XcxLoginResp.class);
    }

    /**
     * 小程序获取 access_token
     *
     * @return access_token
     */
    public static String xcxGetAccessToken() {
        //从缓存获取accessToken
        String accessToken = staticRedisTemplate.opsForValue().get(REDIS_KEY_XCX_ACCESS_TOKEN);
        if (StrUtil.isNotBlank(accessToken)) {
            return accessToken;
        }
        //缓存不存在, 重新申请
        Map<String, Object> param = new HashMap<>();
        param.put("appid", XCX_APP_ID);
        param.put("secret", XCX_APP_SECRET);
        param.put("grant_type", "client_credential");
        String response = HttpUtil.get(XCX_GET_ACCESS_TOKEN_URL, param);
        //解析
        JSONObject jsonObject = JSON.parseObject(response);
        accessToken = jsonObject.getString("access_token");
        if (StrUtil.isBlank(accessToken)) {
            count++;
            if (count > TRY_COUNT) {
                count = 0;
                throw new RuntimeException("获取access_token失败");
            }
            log.error("第" + count + "次尝试获取access_token");
            //获取失败重试
            xcxGetAccessToken();
        }
        count = 0;
        Integer expiresIn = jsonObject.getInteger("expires_in");
        staticRedisTemplate.opsForValue().set(REDIS_KEY_XCX_ACCESS_TOKEN, accessToken, expiresIn - 300, TimeUnit.SECONDS);
        return accessToken;
    }

    /**
     *
     * 小程序发送客服消息
     * 官方API: 微信小程序-服务端-发送客服消息
     * 官方文档地址: https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/kf-mgnt/kf-message/sendCustomMessage.html
     * @param touser   用户的 OpenID
     * @param msgtype  消息类型 text表示文本消息;image表示图片消息;link表示图文链接;miniprogrampage表示小程序卡片。
     * @param content  发送的内容
     * */
    public static void xcxSendCustomMessage(String touser, String msgtype, String content) {
        Map<String, Object> map = new HashMap<>();
        map.put("touser", touser);
        map.put("msgtype", msgtype);
        Map<String, Object> contentMap = new HashMap<>();
        contentMap.put("content", content);
        map.put("text", contentMap);
        String response = HttpUtil.post(XCX_SEND_CUSTOM_MESSAGE_URL + "?access_token=" + xcxGetAccessToken(), JSON.toJSONString(map));
        if (StrUtil.isNotBlank(response)) {
            JSONObject jsonObject = JSON.parseObject(response);
            Integer errcode = jsonObject.getInteger("errcode");
            if (errcode != 0) {
                log.error("发送客服消息失败: " + response);
                throw new ServiceException("发送客服消息失败");
            }
        }
    }

    /**
     * 小程序获取用户手机号
     *
     * @param code 微信授权code
     * @return 手机号
     */
    public static String xcxGetPhoneNumber(String code) {
        Map<String, String> map = new HashMap<>();
        map.put("code", code);
        String response = null;
        try {
            response = HttpUtil.post(XCX_GET_PHONE_NUMBER_URL + "?access_token=" + xcxGetAccessToken(), JSON.toJSONString(map));
            XcxGetPhoneResp phoneResp = JSON.parseObject(response, XcxGetPhoneResp.class);
            XcxGetPhoneResp.PhoneInfoBean phoneInfo = phoneResp.getPhoneInfo();
            return phoneInfo.getPhoneNumber();
        } catch (Exception e) {
            Object errcode = JSON.parseObject(response).get("errcode");
            if (errcode.equals(40001)) {
                staticRedisTemplate.delete(REDIS_KEY_XCX_ACCESS_TOKEN);
                return xcxGetPhoneNumber(code);
            }
            log.error("获取手机号失败: " + errcode);
            throw new ServiceException("获取手机号失败");
        }
    }

    /**
     * 小程序登录响应模型
     *
     * @author zlj
     * @since 2023/3/3
     */
    @Data
    public static class XcxLoginResp implements Serializable {

        @Serial
        private static final long serialVersionUID = 1L;

        /**
         * 用户唯一标识
         */
        private String openid;
        /**
         * 会话密钥
         */
        private String sessionKey;
        /**
         * 用户在开放平台的唯一标识符
         */
        private String unionid;
        /**
         * 错误码
         */
        private int errcode;
        /**
         * 错误信息
         */
        private String errmsg;
    }

    /**
     * 小程序登录响应模型
     *
     * @author zlj
     * @since 2023/3/3
     */
    @Data
    public static class XcxGetPhoneResp implements Serializable {

        @Serial
        private static final long serialVersionUID = 1L;

        private int errcode;
        private String errmsg;
        private PhoneInfoBean phoneInfo;

        @Data
        public static class PhoneInfoBean implements Serializable {
            private String phoneNumber;
            private String purePhoneNumber;
            private int countryCode;
            private WatermarkBean watermark;

            @Data
            public static class WatermarkBean implements Serializable {
                private int timestamp;
                private String appid;
            }
        }
    }
}
